<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>前端路由</title>
</head>

<body>
    <div>
        <button onclick="myHash.push('/')">首页</button>
        <button onclick="myHash.push('/about')">关于</button>
        <button onclick="myHash.replace('/me')">我的</button>
    </div>
    <script>
        // vue-router4 路由模式 history 和 hash 模式实际 hash 内部就是都是用的 history.pushState 方法实现，
        // hash 路由内部实际也是调用 histroy 只是添加一个 # 前缀

        // 两种路由特点
        // 1. hash 刷新页面不会向服务器发起请求所有页面不报错，不支持服务端渲染(不支持seo优化)
        // 2. history 刷新页面会向服务器发送请求如果资源不存在则报错404，可以支持服务端渲染且路径好看

        const TRAILING_SLASH_RE = /\/$/
        const removeTrailingSlash = (path) => path.replace(TRAILING_SLASH_RE, ''); // 移除两头空格

        let createBaseLocation = () => location.protocol + '//' + location.host; // 创建基础路径拼前缀

        // 处理基础路径
        function normalizeBase(base) {
            if (!base) {
                base = '/'
            }
            if (base[0] !== '/' && base[0] !== '#') base = '/' + base
            return removeTrailingSlash(base)
        }

        function stripBase(pathname, base) {
            if (!base || !pathname.toLowerCase().startsWith(base.toLowerCase()))
                return pathname
            return pathname.slice(base.length) || '/'
        }

        // 计算滚动条位置信息
        const computeScrollPosition = () =>
        ({
            left: window.pageXOffset,
            top: window.pageYOffset,
        })

        // 创建当前路径信息
        function createCurrentLocation(
            base, // 基础路径
            location
        ) {
            const { pathname, search, hash } = location
            // 判断是否有 hash  #, /#, #/, #!, #!/, /#!/
            const hashPos = base.indexOf('#')
            if (hashPos > -1) {
                let slicePos = hash.includes(base.slice(hashPos))
                    ? base.slice(hashPos).length
                    : 1;

                let pathFromHash = hash.slice(slicePos)
                if (pathFromHash[0] !== '/') pathFromHash = '/' + pathFromHash;
                return stripBase(pathFromHash, '')
            }
            const path = stripBase(pathname, base)
            return path + search + hash;
        }

        // 创建状态信息包含基本(back后退地址，forward前进地址以及当前状态，当前路径是否替换，滚动条位置, 当前地址栈中位置)
        function buildState(
            back,
            current,
            forward,
            replaced,
            computeScroll
        ) {
            return {
                back,
                current,
                forward,
                replaced,
                position: window.history.length,
                scroll: computeScroll ? computeScrollPosition() : null,  // 浏览器滚动条信息
            }
        }

        // 获取对象包含 location, state, push, replace 属性
        function useHistoryStateNavigation(base) {
            const { history, location } = window;
            const currentLocation = {
                value: createCurrentLocation(base, location), // 创建当前路径地址
            }
            const historyState = { value: history.state }  // 当前路由状态
            console.log(historyState.value)
            // ! 第一次刷新是没有状态信息需要我们自己维护一个状态
            if (!historyState.value) {
                // 首次刷新使用 replace 替换地址得到 history 状态
                changeLocation(
                    currentLocation.value,
                    {
                        back: null,
                        current: currentLocation.value,
                        forward: null,
                        position: history.length - 1,
                        replaced: true,
                        scroll: null, // 滚动条信息
                    },
                    true // 使用 replice 进行改变贴换当前地址
                );
            }
            // 改变地址栏
            function changeLocation(
                to, // 前往的地址
                state, // 状态数据
                replace // 是否使用 replace
            ) {
                const hashIndex = base.indexOf('#'); // 判断基础路径是否有 hash 值
                // 得到真正需要跳转的地址
                const url =
                    hashIndex > -1
                        ? (location.host && document.querySelector('base') // 是否有主机
                            ? base
                            : base.slice(hashIndex)) + to
                        : createBaseLocation() + base + to
                // 进行一定兼容行处理
                try {
                    history[replace ? 'replaceState' : 'pushState'](state, '', url); // 地址栏改变
                    historyState.value = state; // 将生成的状态同步到路由系统中
                } catch (err) {
                    // 强制导航
                    location[replace ? 'replace' : 'assign'](url)
                }
            }
            // replace 方法
            function replace(to, data) {
                // 合并状态信息
                const state = Object.assign(
                    {},
                    history.state, // window.history.state 状态数据信息
                    buildState(
                        historyState.value.back,
                        to,
                        historyState.value.forward,
                        true
                    ),
                    data, // 用户传入状态数据
                    { position: historyState.value.position } // 地址栈位置因为是贴换所有不增加
                )
                changeLocation(to, state, true)
                currentLocation.value = to
            }
            // push 方法
            function push(to, data) {
                // 获取当前状态
                const currentState = Object.assign(
                    {},
                    historyState.value,
                    history.state,
                    {
                        forward: to, // 得到前进地址
                        scroll: computeScrollPosition(),
                    }
                )
                // 假的跳转(本质是原地跳转)只是为了获取更新状态信息为后续监听状态变化准备
                changeLocation(currentState.current, currentState, true);
                // 获取真正需要跳转状态
                const state = Object.assign(
                    {},
                    buildState(currentLocation.value, to, null, false),
                    { position: currentState.position + 1 },
                    data
                );
                // 改变路径进行跳转
                changeLocation(to, state, false)
                currentLocation.value = to; // 改变当前地址栏数据
            }

            // 返回状态信息
            return {
                location: currentLocation,
                state: historyState,
                push,
                replace,
            }
        }

        // 获取对象包含 listen, destroy 属性
        function useHistoryListeners(
            base, // 基础地址
            historyState, // 当前状态
            currentLocation, // 当前地址
            replace // 是否贴换
        ) {
            let listeners = []; // 存放监听状态变化的回调函数
            let teardowns = [];
            let pauseState = null;  // 暂停状态

            const popStateHandler = ({ state }) => {
                // state 回退后的状态
                const to = createCurrentLocation(base, location); // 去哪的地址字符串
                const from = currentLocation.value; // 从哪里来
                const fromState = historyState.value; // 从哪里状态
                // 用于判断前进还是后退
                let delta = 0;
                // 判断回退有无状态
                if (state) {

                    currentLocation.value = to;
                    historyState.value = state;

                    if (pauseState && pauseState === from) {
                        pauseState = null
                        return
                    }

                    delta = fromState ? state.position - fromState.position : 0; // 计算点击前进还是后退

                } else {
                    replace(to)
                }

                // 发布监听回调函数
                listeners.forEach(listener => {
                    // 执行监听函数把当前地址以及上次地址和是否前进传入
                    listener(currentLocation.value, from, {
                        delta,
                        type: 'pop',
                        direction: delta
                            ? delta > 0
                                ? 'forward'
                                : 'back'
                            : '',
                    })
                })
            }

            function pauseListeners() {
                pauseState = currentLocation.value;
            }

            // 监听用户 (发布订阅模式)
            function listen(callback) {
                listeners.push(callback); // 存放回调函数到队列
                const teardown = () => {
                    const index = listeners.indexOf(callback); // 是否已经存放一个相同的
                    if (index > -1) listeners.splice(index, 1)
                }
                teardowns.push(teardown)
                return teardown
            }

            // 取消监听
            function destroy() {
                for (const teardown of teardowns) teardown()
                teardowns = []
                window.removeEventListener('popstate', popStateHandler)
            }

            // 监听状态变化
            window.addEventListener('popstate', popStateHandler);

            return {
                pauseListeners, // 暂停状态方法
                listen,
                destroy,
            }
        }

        // 创建 history 对象
        function createWebHistory(base) {
            base = normalizeBase(base); // 处理基础路径
            // 获取一个包含 location(当前地址), push(追加地址方法), replace(贴换地址方法), state(包含上下当前地址以及地址在栈中位置和滚动条位置) 对象
            const historyNavigation = useHistoryStateNavigation(base);
            // 监听状态
            const historyListeners = useHistoryListeners(
                base,
                historyNavigation.state,
                historyNavigation.location,
                historyNavigation.replace
            );
            // 跳转地址
            function go(delta, triggerListeners = true) {
                if (!triggerListeners) historyListeners.pauseListeners()
                history.go(delta)
            }
            // 合并两个对象
            const routerHistory = Object.assign(
                {
                    location: '',
                    base,
                    go,
                    // createHref: createHref.bind(null, base),
                },
                historyNavigation,
                historyListeners
            )

            // 进行代理操作方便取值
            Object.defineProperty(routerHistory, 'location', {
                enumerable: true,
                get: () => historyNavigation.location.value,
            });
            Object.defineProperty(routerHistory, 'state', {
                enumerable: true,
                get: () => historyNavigation.state.value,
            });

            return routerHistory
        }

        // const myHistory = createWebHistory()
        // console.log(myHistory);
        // // 监听值
        // myHistory.listen((...args) => {
        //     console.log(...args)
        // })


        function createWebHashHistory(base) {
            base = location.host ? base || location.pathname + location.search : ''
            if (!base.includes('#')) base += '#'
            return createWebHistory(base)
        }

        const myHash = createWebHashHistory("#")
        myHash.listen((...args) => {
            console.log(...args)
        })

        // 路由基本信息都包含当前路径，状态，push replace 方法以及监听路由变化通知用户
        // vue-router4 与跟 react-router4 实现区别还是有 vue-router4 中 hash 路由实际就是用的 history 路由并且没有依赖 history 第三方库
    </script>

</body>

</html>